/*
 * CC3Texture.m
 *
 * cocos3d 0.6.1
 * Author: Bill Hollings
 * Copyright (c) 2011 The Brenwill Workshop Ltd. All rights reserved.
 * http://www.brenwill.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * http://en.wikipedia.org/wiki/MIT_License
 * 
 * See header file CC3Texture.h for full API documentation.
 */

#include "CC3Texture.h"
#include "CCTextureCache.h"
#include "CC3OpenGLES11Engine.h"

CC3Texture::CC3Texture() :
	texture(NULL),
	textureUnit(NULL)
{
}

CC3Texture::~CC3Texture()
{
	texture->release();
	if (textureUnit) 
	{
		textureUnit->release();
	}
}

cocos2d::CCTexture2D* CC3Texture::getTexture()
{
	return texture;
}

void CC3Texture::setTexture(cocos2d::CCTexture2D* tex)
{
	cocos2d::CCTexture2D* oldTex = texture;
	texture = tex;
	texture->retain();
	if (oldTex) 
	{
		oldTex->release();
	}
	
	updateTexture2DWithParameters();
}

void CC3Texture::updateTexture2DWithParameters()
{
	texture->setTexParameters(&textureParameters);
}

CC3TextureUnit* CC3Texture::getTextureUnit()
{
	return textureUnit;
}

void CC3Texture::setTextureUnit(CC3TextureUnit* newValue)
{
	CC3TextureUnit* oldTextureUnit = textureUnit;
	textureUnit = newValue;
	textureUnit->retain();
	if (oldTextureUnit) 
	{
		oldTextureUnit->release();
	}
}

cocos2d::ccTexParams CC3Texture::getTextureParameters()
{
	return textureParameters;
}

void CC3Texture::setTextureParameters(cocos2d::ccTexParams texParams)
{
	textureParameters = texParams;
	updateTexture2DWithParameters();
}

cocos2d::ccTex2F CC3Texture::getMapSize()
{
	cocos2d::ccTex2F st;
	if (texture) 
	{
		st.u = texture->getMaxS();
		st.v = texture->getMaxT();
	} 
	else 
	{
		st.u = 0.0f;
		st.v = 0.0f;
	}
	return st;
}

bool CC3Texture::getHasPremultipliedAlpha()
{
	return (texture && texture->getHasPremultipliedAlpha());
}

CC3Vector CC3Texture::getLightDirection()
{
	return textureUnit ? textureUnit->getLightDirection() : kCC3VectorZero;
}

void CC3Texture::setLightDirection(CC3Vector newValue)
{
	textureUnit->setLightDirection(newValue);
}

bool CC3Texture::getIsBumpMap()
{
	return textureUnit && textureUnit->getIsBumpMap();
}

bool CC3Texture::initFromFile(const char* aFileName)
{
	return initWithNameAndFile(aFileName, aFileName);
}

CC3Texture* CC3Texture::textureFromFile(const char* aFileName)
{
	CC3Texture* pTexture = new CC3Texture;
	if (pTexture) 
	{
		pTexture->initFromFile(aFileName);
		pTexture->autorelease();
	}
	
	return pTexture;
}

bool CC3Texture::initWithTagAndFile(GLuint aTag, const char* aFileName)
{
	return initWithTagNameAndFile(aTag, aFileName, aFileName);
}

CC3Texture* CC3Texture::textureWithTagAndFile(GLuint aTag, const char* aFileName)
{
	CC3Texture* pTexture = new CC3Texture;
	if (pTexture) 
	{
		pTexture->initWithTagAndFile(aTag, aFileName);
		pTexture->autorelease();
	}
	
	return pTexture;
}

bool CC3Texture::initWithNameAndFile(const char* aName, const char* aFileName)
{
	return initWithTagNameAndFile(getNextTag(), aName, aFileName);
}

CC3Texture* CC3Texture::textureWithNameAndFile(const char* aName, const char* aFileName)
{
	CC3Texture* pTexture = new CC3Texture;
	if (pTexture) 
	{
		pTexture->initWithNameAndFile(aName, aFileName);
		pTexture->autorelease();
	}
	
	return pTexture;
}

bool CC3Texture::initWithTagNameAndFile(GLuint aTag, const char* aName, const char* aFileName)
{
	if ( initWithTagAndName(aTag, aName) ) 
	{
		if ( !loadTextureFile(aFileName) ) 
		{
			release();
			return false;
		}
	}
			
	return true;
}
			
CC3Texture* CC3Texture::textureWithTagNameAndFile(GLuint aTag, const char* aName, const char* aFileName)
{
	CC3Texture* pTexture = new CC3Texture;
	if (pTexture) 
	{
		pTexture->initWithTagNameAndFile(aTag, aName, aFileName);
		pTexture->autorelease();
	}
	
	return pTexture;
}
			
bool CC3Texture::loadTextureFile(const char* aFileName)
{
	setTexture(cocos2d::CCTextureCache::sharedTextureCache()->addImage(aFileName));
	
	if (texture) 
	{
		//LogTrace(@"%@ loaded texture from file %@", self, aFileName);
		return true;
	} 
	else 
	{
		//LogError(@"%@ could not load texture from file %@", self, aFileName);
		return false;
	}
}

bool CC3Texture::initWithTagAndName(GLuint aTag, const char* aName)
{
	if ( CC3Identifiable::initWithTag(aTag, aName) ) 
	{
		textureParameters = kCC3DefaultTextureParameters;
		texture = NULL;
		textureUnit = NULL;
	}
	return true;
}
			
static GLuint lastAssignedTextureTag = 0;

GLuint CC3Texture::getNextTag()
{
	return ++lastAssignedTextureTag;
}

void CC3Texture::resetTagAllocation()
{
	lastAssignedTextureTag = 0;
}

void CC3Texture::drawWithVisitor(CC3NodeDrawingVisitor* visitor)
{
	if (texture) 
	{
		bindGLWithVisitor(visitor);
		visitor->setTextureUnit(visitor->getTextureUnit() + 1);
	}
}
			
void CC3Texture::bindGLWithVisitor(CC3NodeDrawingVisitor* visitor)
{
	CC3OpenGLES11TextureUnit* gles11TexUnit = CC3OpenGLES11Engine::engine()->getTextures()->textureUnitAt(visitor->getTextureUnit());
	gles11TexUnit->getTexture2D()->enable();
	gles11TexUnit->getTextureBinding()->setValue(texture->getName());
	bindToGLTextureUnit(gles11TexUnit, visitor);
	
	//LogTrace(@"%@ bound to %@", self, gles11TexUnit);
}

void CC3Texture::bindToGLTextureUnit(CC3OpenGLES11TextureUnit* gles11TexUnit, CC3NodeDrawingVisitor* visitor)
{
	if (textureUnit) 
	{
		textureUnit->bindTo(gles11TexUnit, visitor);
	} 
	else 
	{
		CC3TextureUnit::bindDefaultTo(gles11TexUnit);
	}
}

void CC3Texture::unbind(GLuint texUnit)
{
	CC3OpenGLES11Engine::engine()->getTextures()->textureUnitAt(texUnit)->getTexture2D()->disable();
}

void CC3Texture::unbindRemainingFrom(GLuint textureUnit)
{
	GLuint maxTexUnits = CC3OpenGLES11Engine::engine()->getTextures()->textureUnitCount();
	for (int tu = textureUnit; tu < maxTexUnits; tu++) 
	{
		unbind(tu);
	}
}

void CC3Texture::unbind()
{
	unbindRemainingFrom(0);
}
			
void CC3Texture::populateFrom(CC3Texture* another)
{
	CC3Identifiable::populateFrom(another);
	
	// The 2D texture is not copied, but instead retained by reference, and shared between instances.
	texture->release();
	texture = another->getTexture();
	texture->retain();				// retained
	
	textureUnit->release();
	textureUnit = another->getTextureUnit();
	//textureUnit->copy();	// retained
	
	textureParameters = another->getTextureParameters();
}

//@interface CC3Identifiable (TemplateMethods)
//-(void) populateFrom: (CC3Identifiable*) another;
//@end
//
//@interface CC3Texture (TemplateMethods)
//-(void) updateTexture2DWithParameters;
//-(void) drawMainWithVisitor: (CC3NodeDrawingVisitor*) visitor;
//-(void) drawOverlaysWithVisitor: (CC3NodeDrawingVisitor*) visitor;
//-(void) bindGLWithVisitor: (CC3NodeDrawingVisitor*) visitor;
//-(void) bindToGLTextureUnit: (CC3OpenGLES11TextureUnit*) gles11TexUnit
//				withVisitor: (CC3NodeDrawingVisitor*) visitor;
//@end
//
//
//@implementation CC3Texture
//
//@synthesize texture, textureUnit, textureParameters;
//
//-(void) dealloc {
//	[texture release];
//	[textureUnit release];
//	[super dealloc];
//}
//
//-(void) setTexture:(CCTexture2D *) tex {
//	id oldTex = texture;
//	texture = [tex retain];
//	[oldTex release];
//	[self updateTexture2DWithParameters];
//}
//
//-(void) setTextureParameters:(ccTexParams) texParams {
//	textureParameters = texParams;
//	[self updateTexture2DWithParameters];
//}
//
//-(ccTex2F) mapSize {
//	ccTex2F st;
//	if (texture) {
//		st.u = texture.maxS;
//		st.v = texture.maxT;
//	} else {
//		st.u = 0.0f;
//		st.v = 0.0f;
//	}
//	return st;
//}
//
//-(BOOL) hasPremultipliedAlpha {
//	return (texture && texture.hasPremultipliedAlpha);
//}
//
///** Updates the contained CCTexture2D instance with the specified parameters. */
//-(void) updateTexture2DWithParameters {
//	[texture setTexParameters: &textureParameters];
//}
//
//-(CC3Vector) lightDirection {
//	return textureUnit ? textureUnit.lightDirection : kCC3VectorZero;
//}
//
//-(void) setLightDirection: (CC3Vector) aDirection {
//	textureUnit.lightDirection = aDirection;
//}
//
//-(BOOL) isBumpMap {
//	return textureUnit && textureUnit.isBumpMap;
//}
//
//
//#pragma mark Allocation and Initialization
//
//-(id) initFromFile: (NSString*) aFileName {
//	return [self initWithName: aFileName fromFile: aFileName];
//}
//
//+(id) textureFromFile: (NSString*) aFileName {
//	return [[[self alloc] initFromFile: aFileName] autorelease];
//}
//
//-(id) initWithTag: (GLuint) aTag fromFile: (NSString*) aFileName {
//	return [self initWithTag: aTag withName: aFileName fromFile: aFileName];
//}
//
//+(id) textureWithTag: (GLuint) aTag fromFile: (NSString*) aFileName {
//	return [[[self alloc] initWithTag: aTag fromFile: aFileName] autorelease];
//}
//
//-(id) initWithName: (NSString*) aName fromFile: (NSString*) aFileName {
//	return [self initWithTag: [self nextTag] withName: aName fromFile: aFileName];
//}
//
//+(id) textureWithName: (NSString*) aName fromFile: (NSString*) aFileName {
//	return [[[self alloc] initWithName: aName fromFile: aFileName] autorelease];
//}
//
//-(id) initWithTag: (GLuint) aTag withName: (NSString*) aName fromFile: (NSString*) aFileName {
//	if ( (self = [self initWithTag: aTag withName: aName]) ) {
//		if ( ![self loadTextureFile: aFileName] ) {
//			[self release];
//			return nil;
//		}
//	}
//	return self;
//}
//
//+(id) textureWithTag: (GLuint) aTag withName: (NSString*) aName fromFile: (NSString*) aFileName {
//	return [[[self alloc] initWithTag: aTag withName: aName fromFile: aFileName] autorelease];
//}
//
//-(id) initWithTag: (GLuint) aTag withName: (NSString*) aName {
//	if ( (self = [super initWithTag: aTag withName: aName]) ) {
//		textureParameters = kCC3DefaultTextureParameters;
//		texture = nil;
//		textureUnit = nil;
//	}
//	return self;
//}
//
//-(BOOL) loadTextureFile: (NSString*) aFileName {
//	self.texture = [[CCTextureCache sharedTextureCache] addImage: aFileName];
//	if (texture) {
//		LogTrace(@"%@ loaded texture from file %@", self, aFileName);
//		return YES;
//	} else {
//		LogError(@"%@ could not load texture from file %@", self, aFileName);
//		return NO;
//	}
//}
//
//// Template method that populates this instance from the specified other instance.
//// This method is invoked automatically during object copying via the copyWithZone: method.
//-(void) populateFrom: (CC3Texture*) another {
//	[super populateFrom: another];
//	
//	// The 2D texture is not copied, but instead retained by reference, and shared between instances.
//	[texture release];
//	texture = [another.texture retain];				// retained
//
//	[textureUnit release];
//	textureUnit = [another.textureUnit copy];	// retained
//
//	textureParameters = another.textureParameters;
//}
//
//
//#pragma mark Tag allocation
//
//// Class variable tracking the most recent tag value assigned for CC3Textures.
//// This class variable is automatically incremented whenever the method nextTag is called.
//static GLuint lastAssignedTextureTag;
//
//-(GLuint) nextTag {
//	return ++lastAssignedTextureTag;
//}
//
//+(void) resetTagAllocation {
//	lastAssignedTextureTag = 0;
//}
//
//
//#pragma mark Drawing
//
//-(void) drawWithVisitor: (CC3NodeDrawingVisitor*) visitor {
//	if (texture) {
//		[self bindGLWithVisitor: visitor];
//		visitor.textureUnit += 1;
//	}
//}
//
//-(void) bindGLWithVisitor: (CC3NodeDrawingVisitor*) visitor {
//	CC3OpenGLES11TextureUnit* gles11TexUnit = [[CC3OpenGLES11Engine engine].textures textureUnitAt: visitor.textureUnit];
//	[gles11TexUnit.texture2D enable];
//	gles11TexUnit.textureBinding.value = texture.name;
//	[self bindToGLTextureUnit: gles11TexUnit withVisitor: visitor];
//	
//	LogTrace(@"%@ bound to %@", self, gles11TexUnit);
//}
//
///**
// * If the texture property is not nil, binds this texture to the  GL engine, in the
// * specified texture unit. If the texture property is nil, invokes the bindDefaultTo:
// * method to disable texture handling in the GL engine.
// */
//-(void) bindToGLTextureUnit: (CC3OpenGLES11TextureUnit*) gles11TexUnit
//				withVisitor: (CC3NodeDrawingVisitor*) visitor {
//	if (textureUnit) {
//		[textureUnit bindTo: gles11TexUnit withVisitor: visitor];
//	} else {
//		[CC3TextureUnit bindDefaultTo: gles11TexUnit];
//	}
//}
//
//+(void) unbind: (GLuint) texUnit {
//	[[[CC3OpenGLES11Engine engine].textures textureUnitAt: texUnit].texture2D disable];
//}
//
//+(void) unbindRemainingFrom: (GLuint)texUnit {
//	GLuint maxTexUnits = [CC3OpenGLES11Engine engine].textures.textureUnitCount;
//	for (int tu = texUnit; tu < maxTexUnits; tu++) {
//		[self unbind: tu];
//	}
//}
//
//+(void) unbind {
//	[self unbindRemainingFrom: 0];
//}

//@end
